package org.example.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Slf4j
public class CsvInMysqlService {
    @Value("${file.template}")
    private String templatePath;

    @PostConstruct
    public void init() {
        log.info("CsvInMysqlService初始化");
        log.info("模板文件路径: {}", templatePath);
        testDatabaseConnection();
    }

    /**
     * 测试数据库连接
     */
    private void testDatabaseConnection() {
        try (Connection conn = dataSource.getConnection()) {
            DatabaseMetaData metaData = conn.getMetaData();
            log.info("数据库连接成功");
            log.info("数据库类型: {}", metaData.getDatabaseProductName());
            log.info("数据库版本: {}", metaData.getDatabaseProductVersion());
            log.info("JDBC驱动版本: {}", metaData.getDriverVersion());
        } catch (SQLException e) {
            log.error("数据库连接测试失败: {}", e.getMessage());
            log.error("详细错误", e);
        }
    }

    @Autowired
    private DataSource dataSource;


    private static final String SPLIT_SEQUENCE = "\u0000";
    private  static final char splitChar = '\u0000';
    /**
     * 异步导入template目录下的所有CSV文件到对应的数据库表中
     * 使用多线程并行导入，立即返回不等待完成
     *
     * @throws Exception 如果启动导入任务时发生错误
     */
    public void importAllCsvFiles() throws Exception {
        log.info("开始扫描CSV文件目录: {}", templatePath);
        // 获取template目录下的所有CSV文件
        List<File> csvFiles = listCsvFiles();
        if (csvFiles.isEmpty()) {
            log.error("在目录 {} 中未找到CSV文件", templatePath);
            return;
        }

        // 记录找到的CSV文件信息
        log.info("找到 {} 个CSV文件待导入:", csvFiles.size());
        csvFiles.forEach(file -> 
            log.info("- {}: {} MB", file.getName(), file.length() / (1024 * 1024)));

        // 计算总数据量
        long totalSize = csvFiles.stream().mapToLong(File::length).sum();
        log.info("总数据量: {} MB", totalSize / (1024 * 1024));

        // 创建线程池，线程数为可用处理器数量的2倍
        int threadCount = Runtime.getRuntime().availableProcessors() * 5;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);

        // 记录开始时间
        long startTime = System.currentTimeMillis();

        // 为每个CSV文件创建一个导入任务
        for (File csvFile : csvFiles) {
            String fileName = csvFile.getName();
            // 获取不带扩展名的文件名作为表名
            String tableName = fileName.substring(0, fileName.lastIndexOf('.'));

            CompletableFuture.runAsync(() -> {
                try {
                    log.info("开始导入文件: {} 到表: {}", fileName, tableName);
                    int rows = importCsvToTable(csvFile, tableName);
                    long endTime = System.currentTimeMillis();
                    double timeInMinutes = (endTime - startTime) / 60000.0;
                    log.info("文件 {} 导入完成: 成功导入 {} 条记录到表: {}, 耗时: {}分钟",
                            fileName, rows, tableName, timeInMinutes);
                } catch (Exception e) {
                    log.error("导入文件 {} 到表 {} 失败", fileName, tableName, e);
                }
            }, executor).exceptionally(throwable -> {
                log.error("处理文件 {} 时发生异常", fileName, throwable);
                return null;
            });
        }

        // 所有任务已提交，可以安全地调用shutdown
        // shutdown()方法会拒绝新任务，但会继续执行已提交的任务直到完成
        executor.shutdown();
        
        // 注册JVM关闭钩子，确保在应用关闭时线程池能够正常关闭
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            log.info("应用关闭，等待CSV导入任务完成...");
            try {
                // 尝试等待所有任务完成，但最多等待5分钟
                if (!executor.isTerminated()) {
                    executor.awaitTermination(50, java.util.concurrent.TimeUnit.MINUTES);
                }
            } catch (InterruptedException e) {
                log.error("等待CSV导入任务完成时被中断", e);
            } finally {
                // 如果仍有任务在运行，尝试强制关闭
                if (!executor.isTerminated()) {
                    log.warn("强制关闭未完成的CSV导入任务");
                    executor.shutdownNow();
                }
                log.info("所有CSV导入任务已关闭");
            }
        }));

        log.info("已启动 {} 个CSV文件的异步导入任务", csvFiles.size());
    }

    /**
     * 列出template目录下的所有CSV文件
     *
     * @return CSV文件列表
     * @throws IOException 如果读取目录失败
     */
    private List<File> listCsvFiles() throws IOException {
        Path dir = Paths.get(templatePath);
        if (!Files.exists(dir)) {
            log.error("目录不存在: {}", templatePath);
            return new ArrayList<>();
        }

        try (Stream<Path> paths = Files.walk(dir, 1)) {
            return paths
                    .filter(Files::isRegularFile)
                    .filter(path -> path.toString().toLowerCase().endsWith(".csv"))
                    .map(Path::toFile)
                    .collect(Collectors.toList());
        }
    }

    /**
     * 根据表名导入CSV文件数据到MySQL表中
     *
     * @param tableName 数据库表名
     * @return 导入的记录数
     * @throws Exception 如果导入过程中发生错误
     */
    @Transactional(rollbackFor = Exception.class)
    public int importCsvToTable(String tableName) throws Exception {
        // 构建CSV文件路径
        String csvFilePath = templatePath + tableName + ".csv";
        File csvFile = new File(csvFilePath);

        if (!csvFile.exists()) {
            throw new IOException("CSV文件不存在: " + csvFilePath);
        }

        return importCsvToTable(csvFile, tableName);
    }

    /**
     * 导入指定的CSV文件到MySQL表中
     *
     * @param csvFile CSV文件
     * @param tableName 数据库表名
     * @return 导入的记录数
     * @throws Exception 如果导入过程中发生错误
     */
//    @Transactional(rollbackFor = Exception.class)
    public int importCsvToTable(File csvFile, String tableName) throws Exception {
        if (!csvFile.exists()) {
            log.error("CSV文件不存在: {}", csvFile.getAbsolutePath());
            throw new IOException("CSV文件不存在: " + csvFile.getAbsolutePath());
        }

        long fileSize = csvFile.length();
        log.info("开始导入CSV文件: {} (大小: {} MB) 到表: {}", 
                csvFile.getAbsolutePath(), 
                String.format("%.2f", fileSize / (1024.0 * 1024.0)), 
                tableName);

        // 获取表结构信息
        log.debug("正在获取表 {} 的结构信息...", tableName);
        List<String> columnNames = getTableColumns(tableName);
        if (columnNames.isEmpty()) {
            log.error("无法获取表 {} 的列信息", tableName);
            throw new SQLException("无法获取表 " + tableName + " 的列信息");
        }
        log.debug("成功获取表 {} 的结构信息: {} 列", tableName, columnNames.size());

        // 记录开始时间
        long startTime = System.currentTimeMillis();

        try {
            // 读取CSV文件并导入数据
            int importedRows = importDataFromCsv(csvFile, tableName, columnNames);

            // 计算总耗时和性能指标
            long endTime = System.currentTimeMillis();
            double totalTimeSeconds = (endTime - startTime) / 1000.0;
            double averageSpeed = fileSize / (totalTimeSeconds * 1024 * 1024); // MB/s
            double rowsPerSecond = importedRows / totalTimeSeconds;

            log.info("表 {} 导入完成 - 统计信息:", tableName);
            log.info("- 总记录数: {} 行", importedRows);
            log.info("- 总耗时: {} 分 {} 秒", 
                    (int)(totalTimeSeconds / 60), 
                    (int)(totalTimeSeconds % 60));
            log.info("- 平均速度: {} MB/s", averageSpeed);
            log.info("- 处理速率: {} 行/秒", rowsPerSecond);

            return importedRows;
        } catch (Exception e) {
            log.error("导入CSV文件到表 {} 时发生错误: {}", tableName, e.getMessage());
            log.debug("详细错误信息", e);
            throw e;
        }
    }

    /**
     * 获取表的列名
     *
     * @param tableName 表名
     * @return 列名列表
     * @throws SQLException 如果获取列信息失败
     */
    private List<String> getTableColumns(String tableName) throws SQLException {
        List<String> columns = new ArrayList<>();

        try (Connection conn = dataSource.getConnection()) {
            DatabaseMetaData metaData = conn.getMetaData();
            ResultSet rs = metaData.getColumns(null, null, tableName, null);

            while (rs.next()) {
                String columnName = rs.getString("COLUMN_NAME");
                columns.add(columnName);
            }
        }

        return columns;
    }

    /**
     * 从CSV文件导入数据到指定表
     *
     * @param csvFile CSV文件
     * @param tableName 表名
     * @param columnNames 列名列表
     * @return 导入的记录数
     * @throws IOException 如果读取CSV文件失败
     * @throws SQLException 如果数据库操作失败
     */
    private int importDataFromCsv(File csvFile, String tableName, List<String> columnNames) throws IOException, SQLException {
        int importedRows = 0;

        // 构建SQL插入语句
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append("INSERT INTO ").append(tableName).append(" (");

        for (int i = 0; i < columnNames.size(); i++) {
            if (i > 0) {
                sqlBuilder.append(", ");
            }
            sqlBuilder.append(columnNames.get(i));
        }

        sqlBuilder.append(") VALUES (");

        for (int i = 0; i < columnNames.size(); i++) {
            if (i > 0) {
                sqlBuilder.append(", ");
            }
            sqlBuilder.append("?");
        }

        sqlBuilder.append(")");

        String sql = sqlBuilder.toString();

        try (Connection conn = dataSource.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql);
             BufferedReader reader = new BufferedReader(new FileReader(csvFile))) {

            String line;
            boolean isFirstLine = true;

            // 设置更大的批处理大小，减少数据库交互
            final int batchSize = 5000;
            int count = 0;
            long startTime = System.currentTimeMillis();
            long lastProgressTime = startTime;
            long totalBytes = csvFile.length();
            long processedBytes = 0;

            int isend = 0;
            while ((line = readCsvLine(reader,isFirstLine,columnNames.size())) != null) {
                if(StringUtils.isEmpty(line)){
                    isend++;
                    if(isend>10){
                        break;
                    }
                    continue;
                }
                // 更新处理进度
                processedBytes += line.length() + 1; // +1 for newline character
                isFirstLine = false;

                // 解析CSV行
                String[] values = parseCsvLine(line);

                // 确保值的数量与列数匹配
                if (values.length != columnNames.size()) {
                    log.info("CSV行的值数量 ({}) 与表列数 ({}) 不匹配，跳过该行",
                            values.length, columnNames.size());
                    continue;
                }

                // 设置参数值
                for (int i = 0; i < values.length; i++) {
                    if(StringUtils.isEmpty(values[i])) values[i] = null;
                    pstmt.setString(i + 1, values[i]);
                }

                // 添加到批处理
                pstmt.addBatch();
                count++;

                // 当达到批处理大小时执行批处理
                if (count % batchSize == 0) {
                    int[] results = pstmt.executeBatch();
                    importedRows += Arrays.stream(results).sum();

                    // 每30秒或处理完1%的数据时输出进度
                    long currentTime = System.currentTimeMillis();
                    if (currentTime - lastProgressTime > 30000 ||
                        (processedBytes * 100 / totalBytes) > (((count - batchSize) * 100) / count)) {

                        double progress = (double) processedBytes / totalBytes * 100;
                        double speed = processedBytes / ((currentTime - startTime) / 1000.0) / (1024 * 1024); // MB/s
                        long remainingSeconds = (long) ((totalBytes - processedBytes) / (processedBytes / ((currentTime - startTime) / 1000.0)));

                        log.info("表 {}: 已处理 {} 条记录 ({}%), {} MB/s, 总消耗时间: {}",
                                tableName, importedRows, progress, speed,
                                String.format("%d:%02d:%02d",
                                    remainingSeconds / 3600,
                                    (remainingSeconds % 3600) / 60,
                                    remainingSeconds % 60));

                        lastProgressTime = currentTime;

                        // 建议JVM进行垃圾回收
                        System.gc();
                    }
                }
            }

            // 执行剩余的批处理
            if (count % batchSize != 0) {
                int[] results = pstmt.executeBatch();
                importedRows += Arrays.stream(results).sum();

                // 显示最终进度
                long endTime = System.currentTimeMillis();
                double totalTimeSeconds = (endTime - startTime) / 1000.0;
                double averageSpeed = processedBytes / totalTimeSeconds / (1024 * 1024); // MB/s

                log.info("表 {} 导入完成: 共处理 {} 条记录, 平均速度 {} MB/s, 总耗时: {}",
                        tableName, importedRows, averageSpeed,
                        String.format("%d:%02d:%02d",
                            (long)(totalTimeSeconds / 3600),
                            (long)((totalTimeSeconds % 3600) / 60),
                            (long)(totalTimeSeconds % 60)));
            }


        }

        return importedRows;
    }

    /**
     * 检查字符串的最后一个非空白字符是否为双引号
     *
     * @param line 要检查的字符串
     * @return 如果最后一个非空白字符是双引号返回true，否则返回false
     */
    private boolean isLastCharQuote(String line) {
        for (int i = line.length() - 1; i >= 0; i--) {
            char c = line.charAt(i);
            if (!Character.isWhitespace(c)) {
                return c == '"'||c==splitChar;
            }
        }
        return false;
    }

    /**
     * 读取CSV行，处理包含换行符的字段
     *
     * @param reader BufferedReader实例
     * @return 完整的CSV行，如果到达文件末尾则返回null
     * @throws IOException 如果读取出错
     */
    private String readCsvLine(BufferedReader reader,boolean isFirstLine,int size) throws IOException {
        StringBuilder line = new StringBuilder();
        String currentLine;
        boolean inQuotes = false;


        while ((currentLine = reader.readLine()) != null) {
            if (isFirstLine) {
                isFirstLine = false;
                continue;
            } else {
                if(line.length()>0){
                    line.append("\n").append(currentLine);
                }else{
                    line.append(currentLine);
                }

            }

            int count=0;
            // 计算当前行中的分割符号数量
            int splitLen = SPLIT_SEQUENCE.length();

            for (int i = 0; i <= line.length() - splitLen; i++) {
                if (line.toString().regionMatches(i, SPLIT_SEQUENCE, 0, splitLen)) {
                    count++;

                    // 如果你要跳过整个分隔符长度以避免重叠匹配，可以启用下一行
                    // i += splitLen - 1;
                }
            }
            if(count==size-1){
                inQuotes = false;
            }else{
                inQuotes = true;
            }

            // 检查是否需要继续读取下一行
            // 1. 如果分隔符数量等于字段数减1，说明是完整的CSV行
            // 2. 并且最后一个非空白字符是双引号或者是分隔符，也说明是完整的CSV行
            if (!inQuotes) {
                return line.toString();
            }
        }

        // 如果到达文件末尾
        return isFirstLine ? null : line.toString();
    }

    /**
     * 解析CSV行，处理引号和逗号
     *
     * @param line CSV行
     * @return 解析后的值数组
     */
    private String[] parseCsvLine(String line) {
        List<String> values = new ArrayList<>();
        int start = 0;
        int splitLen = SPLIT_SEQUENCE.length();
        // 使用splitChar分割行

        // 遍历字符串查找分隔符
        for (int i = 0; i <= line.length() - splitLen; i++) {
            if (line.regionMatches(i, SPLIT_SEQUENCE, 0, splitLen)) {
                // 找到分隔符，截取字段
                String field = line.substring(start, i);

                // 去除字段两边的双引号（如果有的话）
                if (field.startsWith("\"") && field.endsWith("\"")) {
                    field = field.substring(1, field.length() - 1);
                }

                values.add(field);
                start = i + splitLen;
                i = start - 1; // 调整索引位置
            }
        }

        // 添加最后一个字段
        String lastField = line.substring(start);
        if (lastField.startsWith("\"") && lastField.endsWith("\"")) {
            lastField = lastField.substring(1, lastField.length() - 1);
        }
        values.add(lastField);

        return values.toArray(new String[0]);

    }
}
